package main

import (
	"TODecider/src/logger"
	"TODecider/src/pb"
	"TODecider/src/util"
	"context"
	"flag"
	"fmt"
	"github.com/go-zeromq/zmq4"
	"github.com/golang/protobuf/proto"
	"github.com/shopspring/decimal"
	"math/rand"
	"regexp"
	"strconv"
	"strings"
	"time"
)

const (
	TickerPeriod = 5
)

var (
	pubPort          int
	mcIp             string
	hostIp           string
	allIp            string
	nodes            []string
	comNetBytes      []byte
	comNetBriefBytes []byte
)

func init() {
	flag.IntVar(&pubPort, "pub", 5555, "Port of Publisher (default 5555)")
	flag.StringVar(&mcIp, "mc", "abc", "K3S Master node URL to obtain IP (default 'abc')")
	flag.StringVar(&allIp, "all", "abc", "Simulated IP list (default 'abc')")
}

func main() {
	flag.Parse()
	log := logger.GetLogger()
	// parse host ip
	b := util.IsLegalIP(mcIp)
	if !b {
		// fixed nodes
		nodes = strings.Split(allIp, ",")
		hostIp = nodes[0]
		log.Infof("[OS] input IP invalid, hostIp will be set as %s", hostIp)
	} else {
		// dynamic nodes
		hostIp = util.ObtainHostIPBy(fmt.Sprintf("http://%s:9090/my_ip", mcIp))
		log.Infof("[OS] hostIp is %s", hostIp)
	}
	// start publisher
	pub := zmq4.NewPub(context.Background())
	defer pub.Close()
	err := pub.Listen(fmt.Sprintf("tcp://0.0.0.0:%d", pubPort))
	if err != nil {
		log.Errorf("[ZMQ Pub] could not listen: %v", err)
	}
	// deal ip and send msg periodically
	tickerM := time.NewTicker(time.Second * TickerPeriod)
	for {
		// dynamic topology
		if b {
			allIp = util.RequestHttp(fmt.Sprintf("http://%s:9090/ip_list", mcIp))
			re, _ := regexp.Compile("{|}|\"|k3s-node[0-9]|:")
			allIp = re.ReplaceAllString(allIp, "")
			nodes = strings.Split(allIp, ",")
		}
		log.Infof("[OS] nodes are %s", nodes)
		// gen comNet
		comNet := genComNet(nodes)
		comNetBrief := genComNetBrief(nodes)
		comNetBytes, err = proto.Marshal(comNet)
		comNetBriefBytes, err = proto.Marshal(comNetBrief)
		if err != nil {
			log.Errorf("[ZMQ Pub] error: %v", err)
		}
		msgA := zmq4.NewMsgFrom(
			[]byte("comnet-brief"),
			comNetBriefBytes,
		)
		msgB := zmq4.NewMsgFrom(
			[]byte("comnet-full"),
			comNetBytes,
		)
		log.Debugf("[ZMQ Pub] sending ComNetBrief: %s", comNetBrief.String())
		err = pub.Send(msgA)
		if err != nil {
			log.Errorf("[ZMQ Pub] error: %v", err)
		}
		log.Debugf("[ZMQ Pub] sending ComNet: %s", comNet.String())
		err = pub.Send(msgB)
		if err != nil {
			log.Errorf("[ZMQ Pub] error: %v", err)
		}
		<-tickerM.C
	}
}

func genComNet(nodes []string) *pb.ComNet {
	comNet := pb.ComNet{}
	comNet.Id = genTimestamp()
	comNet.Origin = hostIp
	var comNetNodes []*pb.ComNet_Node
	var comNetLinks []*pb.ComNet_Link
	for _, node := range nodes {
		// update node
		comNetNode := pb.ComNet_Node{
			Ip: node,
			RestComputing: []float64{floatBetween(1, 8),
				floatBetween(1, 3), floatBetween(0.6, 0.95)},
			RestStorage: []float64{floatBetween(200, 1500),
				floatBetween(0.55, 0.9)},
		}
		comNetNodes = append(comNetNodes, &comNetNode)
		// update link
		for _, nodeV := range nodes {
			if node != nodeV {
				comNetLink := pb.ComNet_Link{
					NodeFrom: node,
					NodeTo:   nodeV,
					Rate:     floatBetween(5, 15),
					EsDelay:  floatBetween(0.5, 4.5),
				}
				comNetLinks = append(comNetLinks, &comNetLink)
			}
		}
	}
	comNet.Nodes = comNetNodes
	comNet.Links = comNetLinks
	return &comNet
}

func genComNetBrief(nodes []string) *pb.ComNetBrief {
	return &pb.ComNetBrief{
		Id:      genTimestamp(),
		Node:    nodes,
		Network: []float64{floatBetween(0.55, 0.85), floatBetween(0.55, 0.85)},
	}
}

// genTimestamp generates 13-bit timestamp
func genTimestamp() string {
	return strconv.FormatInt(time.Now().UnixNano()/1e6, 10)
}

func floatBetween(a float64, b float64) float64 {
	v, _ := decimal.NewFromFloat(rand.Float64()*(b-a) + a).Round(2).Float64()
	return v
}
